Add an icon cache validator.
authorMatthias Clasen <mclasen@redhat.com>
Tue, 1 May 2007 20:00:17 +0000 (20:00 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Tue, 1 May 2007 20:00:17 +0000 (20:00 +0000)
2007-05-01  Matthias Clasen  <mclasen@redhat.com>

        * gtk/gtkiconcachvalidator.[hc]: Add an icon cache validator.

        * gtk/updateiconcache.c: Validate the generated cache before
        moving it in place. Also add a --validate option to validate
        an existing icon cache.

        * gtk/gtkiconcache.c: Validate icon caches before using them.

        * gtk/Makefile.am: Integrate it.

svn path=/trunk/; revision=17753

ChangeLog
gtk/Makefile.am
gtk/gtkiconcache.c
gtk/gtkiconcachevalidator.c [new file with mode: 0644]
gtk/gtkiconcachevalidator.h [new file with mode: 0644]
gtk/updateiconcache.c

index 0d887d4ad3f6a078fc82db17d31c0c569593a277..708cf4a6d88aae85c84f0c38508efe6fe81eeb3e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2007-05-01  Matthias Clasen  <mclasen@redhat.com>
+
+       * gtk/gtkiconcachvalidator.[hc]: Add an icon cache validator.
+
+       * gtk/updateiconcache.c: Validate the generated cache before
+       moving it in place. Also add a --validate option to validate
+       an existing icon cache.
+
+       * gtk/gtkiconcache.c: Validate icon caches before using them.
+
+       * gtk/Makefile.am: Integrate it.
+
 2007-05-01  Michael Emmel  <mike.emmel@gmail.com>
 
        * gdk/directfb/gdkdisplay-directfb.c:
index 74e8884c58ac5ed53b55fdce87faa5a2cbc683ec..72481ba87c5d425333cdfe011f314ae303bdb306 100644 (file)
@@ -454,6 +454,7 @@ gtk_base_c_sources =            \
        gtkhseparator.c         \
        gtkhsv.c                \
        gtkiconcache.c          \
+       gtkiconcachevalidator.c \
        gtkiconfactory.c        \
        gtkicontheme.c          \
        gtkiconview.c           \
@@ -610,6 +611,7 @@ gtk_all_c_sources += $(gtk_os_unix_c_sources)
 if OS_UNIX
 gtk_private_h_sources += \
        gtkfilesystemunix.h             \
+       gtkiconcachevalidator.h         \
        gtkprintbackend.h               \
        gtkprinter-private.h            \
        gtkprinteroption.h              \
@@ -865,7 +867,8 @@ gtk_query_immodules_2_0_SOURCES = queryimmodules.c
 
 gtk_update_icon_cache_LDADD = $(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GTK_API_VERSION).la
 
-gtk_update_icon_cache_SOURCES = updateiconcache.c
+gtk_update_icon_cache_SOURCES = \
+       updateiconcache.c 
 
 .PHONY: files test test-debug
 
index a15d8d39c329fa0349275b277ab282bb763916f1..cd25d300f1c463644088446585d143e1f74acff0 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "gtkdebug.h"
 #include "gtkiconcache.h"
+#include "gtkiconcachevalidator.h"
 #include "gtkalias.h"
 
 #include <glib/gstdio.h>
@@ -58,7 +59,7 @@ struct _GtkIconCache {
 GtkIconCache *
 _gtk_icon_cache_ref (GtkIconCache *cache)
 {
-  cache->ref_count ++;
+  cache->ref_count++;
   return cache;
 }
 
@@ -89,6 +90,7 @@ _gtk_icon_cache_new_for_path (const gchar *path)
   struct stat st;
   struct stat path_st;
   gchar *buffer = NULL;
+  CacheInfo info;
 
    /* Check if we have a cache file */
   cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
@@ -121,25 +123,26 @@ _gtk_icon_cache_new_for_path (const gchar *path)
   if (!map)
     goto done;
 
-  /* Verify version */
-  buffer = g_mapped_file_get_contents (map);
-  if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
-      GET_UINT16 (buffer, 2) != MINOR_VERSION)
+  info.cache = g_mapped_file_get_contents (map);
+  info.cache_size = g_mapped_file_get_length (map);
+  info.n_directories = 0;
+  info.flags = CHECK_OFFSETS|CHECK_STRINGS;
+
+  g_print ("validating %s\n", cache_filename);
+  if (!_gtk_icon_cache_validate (&info))
     {
       g_mapped_file_free (map);
 
-      GTK_NOTE (ICONTHEME, 
-               g_print ("wrong cache version\n"));
+      GTK_NOTE (ICONTHEME, g_print ("invalid icon cache\n"));
       goto done;
     }
   
-  GTK_NOTE (ICONTHEME, 
-           g_print ("found cache for %s\n", path));
+  GTK_NOTE (ICONTHEME, g_print ("found cache for %s\n", path));
 
   cache = g_new0 (GtkIconCache, 1);
   cache->ref_count = 1;
   cache->map = map;
-  cache->buffer = buffer;
+  cache->buffer = g_mapped_file_get_contents (map);
 
  done:
   g_free (cache_filename);  
@@ -440,7 +443,7 @@ _gtk_icon_cache_get_icon (GtkIconCache *cache,
   length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
   
   if (!gdk_pixdata_deserialize (&pixdata, length, 
-                               cache->buffer + pixel_data_offset + 8,
+                               (guchar *)(cache->buffer + pixel_data_offset + 8),
                                &error))
     {
       GTK_NOTE (ICONTHEME,
diff --git a/gtk/gtkiconcachevalidator.c b/gtk/gtkiconcachevalidator.c
new file mode 100644 (file)
index 0000000..cb1d964
--- /dev/null
@@ -0,0 +1,376 @@
+/* gtkiconcachevalidator.c
+ * Copyright (C) 2007 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "config.h"
+#include "gtkiconcachevalidator.h"
+
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+
+
+#define VERBOSE(x) 
+
+#define check(name,condition) \
+  if (!(condition)) \
+    { \
+      VERBOSE(g_print ("bad %s\n", (name))); \
+      return FALSE; \
+    } 
+
+static inline gboolean 
+get_uint16 (CacheInfo *info, 
+            guint32    offset, 
+            guint16   *value)
+{
+  if (offset < info->cache_size) 
+    { 
+      *value = GUINT16_FROM_BE(*(guint16*)(info->cache + offset)); 
+      return TRUE;
+    }
+  else 
+    { 
+      *value = 0;
+      return FALSE; 
+    } 
+}
+
+static inline gboolean 
+get_uint32 (CacheInfo *info, 
+            guint32    offset, 
+            guint32   *value)
+{
+  if (offset < info->cache_size) 
+    { 
+      *value = GUINT32_FROM_BE(*(guint32*)(info->cache + offset)); 
+      return TRUE;
+    }
+  else 
+    { 
+      *value = 0;
+      return FALSE; 
+    } 
+}
+
+static gboolean 
+check_version (CacheInfo *info)
+{
+  guint16 major, minor;
+
+  check ("major version", get_uint16 (info, 0, &major) && major == 1);
+  check ("minor version", get_uint16 (info, 2, &minor) && minor == 0);
+
+  return TRUE;
+}
+
+static gboolean 
+check_string (CacheInfo *info, 
+              guint32    offset)
+{
+  check ("string offset", offset < info->cache_size);
+
+  if (info->flags & CHECK_STRINGS) 
+    {
+      gint i;
+      gchar c;
+
+      /* assume no string is longer than 1k */
+      for (i = 0; i < 1024; i++) 
+        { 
+          check ("string offset", offset + i < info->cache_size)
+          c = *(info->cache + offset + i);
+          if (c == '\0')
+            break;
+          check ("string content", g_ascii_isgraph (c));
+        }
+      check ("string length", i < 1024);
+    }
+
+  return TRUE;
+}
+
+static gboolean 
+check_directory_list (CacheInfo *info, 
+                      guint32    offset)
+{
+  guint32 directory_offset;
+  gint i;
+       
+  check ("offset, directory list", get_uint32 (info, offset, &info->n_directories));
+       
+  for (i = 0; i < info->n_directories; i++) 
+    {
+      check ("offset, directory", get_uint32 (info, offset + 4 + 4 * i, &directory_offset));
+      if (!check_string (info, directory_offset))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean 
+check_pixel_data (CacheInfo *info, 
+                  guint32    offset)
+{
+  guint32 type;
+  guint32 length;
+
+  check ("offset, pixel data type", get_uint32 (info, offset, &type));
+  check ("offset, pixel data length", get_uint32 (info, offset + 4, &length));
+
+  check ("pixel data type", type == 0);
+  check ("pixel data length", offset + 8 + length < info->cache_size);
+
+  if (info->flags & CHECK_PIXBUFS) 
+    {
+      GdkPixdata data; 
+      check ("pixel data", gdk_pixdata_deserialize (&data, length,
+                                                    info->cache + offset + 8, 
+                                                    NULL));
+    }
+       
+  return TRUE;
+}
+
+static gboolean 
+check_embedded_rect (CacheInfo *info, 
+                     guint32    offset)
+{
+  check ("embedded rect", offset + 4 < info->cache_size);
+
+  return TRUE;
+}
+
+static gboolean 
+check_attach_point_list (CacheInfo *info, 
+                         guint32    offset)
+{
+  guint32 n_attach_points;
+
+  check ("offset, attach point list", get_uint32 (info, offset, &n_attach_points));
+  check ("attach points", offset + 4 + 4 * n_attach_points < info->cache_size); 
+
+  return TRUE;
+}
+
+static gboolean 
+check_display_name_list (CacheInfo *info, 
+                         guint32    offset)
+{
+  guint32 n_display_names;
+  gint i;
+
+  check ("offset, display name list", 
+         get_uint32 (info, offset, &n_display_names));
+  for (i = 0; i < n_display_names; i++) 
+    {
+      if (!check_string (info, offset + 4 + 8 * i))
+        return FALSE;
+      if (!check_string (info, offset + 4 + 8 * i + 4))
+        return FALSE;
+    }
+       
+  return TRUE; 
+}
+
+static gboolean 
+check_meta_data (CacheInfo *info, 
+                 guint32    offset)
+{
+  guint32 embedded_rect_offset;
+  guint32 attach_point_list_offset;
+  guint32 display_name_list_offset;
+
+  check ("offset, embedded rect", 
+         get_uint32 (info, offset, &embedded_rect_offset));
+  check ("offset, attach point list", 
+         get_uint32 (info, offset + 4, &attach_point_list_offset));
+  check ("offset, display name list", 
+         get_uint32 (info, offset + 8, &display_name_list_offset));
+
+  if (embedded_rect_offset != 0) 
+    {
+      if (!check_embedded_rect (info, embedded_rect_offset))
+        return FALSE;
+    }
+
+  if (attach_point_list_offset != 0) 
+    {
+      if (!check_attach_point_list (info, attach_point_list_offset))
+        return FALSE;
+    }
+
+  if (display_name_list_offset != 0) 
+    {
+      if (!check_display_name_list (info, display_name_list_offset))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean 
+check_image_data (CacheInfo *info, 
+                  guint32    offset)
+{
+  guint32 pixel_data_offset;
+  guint32 meta_data_offset;
+
+  check ("offset, pixel data", get_uint32 (info, offset, &pixel_data_offset));
+  check ("offset, meta data", get_uint32 (info, offset + 4, &meta_data_offset));
+
+  if (pixel_data_offset != 0) 
+    {
+      if (!check_pixel_data (info, pixel_data_offset))
+        return FALSE;
+    }
+  if (meta_data_offset != 0) 
+    {
+      if (!check_meta_data (info, meta_data_offset))
+        return FALSE;
+    }
+       
+  return TRUE;
+}
+
+static gboolean 
+check_image (CacheInfo *info, 
+             guint32    offset)
+{
+  guint16 index;
+  guint16 flags;
+  guint32 image_data_offset;
+
+  check ("offset, image index", get_uint16 (info, offset, &index));
+  check ("offset, image flags", get_uint16 (info, offset + 2, &flags));        
+  check ("offset, image data offset", 
+         get_uint32 (info, offset + 4, &image_data_offset));
+
+  check ("image index", index < info->n_directories);
+  check ("image flags", flags == 1 || flags == 2 || flags == 4 ||
+                        flags == 9 || flags == 10 || flags == 12);
+
+  if (image_data_offset != 0) 
+    {
+      if (!check_image_data (info, image_data_offset))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean 
+check_image_list (CacheInfo *info, 
+                  guint32    offset)
+{
+  guint32 n_images;
+  gint i;
+
+  check ("offset, image list", get_uint32 (info, offset, &n_images));
+       
+  for (i = 0; i < n_images; i++) 
+    {
+      if (!check_image (info, offset + 4 + 8 * i))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean 
+check_icon (CacheInfo *info, 
+            guint32    offset)
+{
+  guint32 chain_offset;
+  guint32 name_offset;
+  guint32 image_list_offset;
+
+  check ("offset, icon chain", get_uint32 (info, offset, &chain_offset));
+  check ("offset, icon name", get_uint32 (info, offset + 4, &name_offset));
+  check ("offset, icon image list", get_uint32 (info, offset + 8, 
+         &image_list_offset));
+
+  if (!check_string (info, name_offset))
+    return FALSE;
+  if (!check_image_list (info, image_list_offset))
+    return FALSE;
+  if (chain_offset != 0xffffffff) 
+    {
+      if (!check_icon (info, chain_offset))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean 
+check_hash (CacheInfo *info, 
+            guint32    offset)
+{
+  guint32 n_buckets, icon_offset;
+  gint i;
+
+  check ("offset, hash size", get_uint32 (info, offset, &n_buckets));
+
+  for (i = 0; i < n_buckets; i++) 
+    {
+      check ("offset, hash chain", 
+             get_uint32 (info, offset + 4 + 4 * i, &icon_offset));
+      if (icon_offset != 0xffffffff) 
+        {
+          if (!check_icon (info, icon_offset))
+            return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+/**
+ * _gtk_icon_cache_validate:
+ * @info: a CacheInfo structure 
+ *
+ * Validates the icon cache passed in the @cache and
+ * @cache_size fields of the @info structure. The
+ * validator checks that offsets specified in the
+ * cache do not point outside the mapped area, that
+ * strings look reasonable, and that pixbufs can
+ * be deserialized. The amount of validation can
+ * be controlled with the @flags field.  
+ *
+ * Return value: %TRUE if the cache is valid
+ */
+gboolean 
+_gtk_icon_cache_validate (CacheInfo *info)
+{
+  guint32 hash_offset;
+  guint32 directory_list_offset;
+
+  if (!check_version (info))
+    return FALSE;
+  check ("header, hash offset", get_uint32 (info, 4, &hash_offset));
+  check ("header, directory list offset", get_uint32 (info, 8, &directory_list_offset));
+  if (!check_directory_list (info, directory_list_offset))
+    return FALSE;
+
+  if (!check_hash (info, hash_offset))
+    return FALSE;
+
+  return TRUE;
+}
+
diff --git a/gtk/gtkiconcachevalidator.h b/gtk/gtkiconcachevalidator.h
new file mode 100644 (file)
index 0000000..9b22e85
--- /dev/null
@@ -0,0 +1,44 @@
+/* gtkiconcachevalidator.4
+ * Copyright (C) 2007 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GTK_ICON_CACHE_VALIDATOR_H__
+#define __GTK_ICON_CACHE_VALIDATOR_H__
+
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+enum {
+  CHECK_OFFSETS = 1,
+  CHECK_STRINGS = 2,
+  CHECK_PIXBUFS = 4
+};
+
+typedef struct {
+  const gchar *cache;
+  gsize cache_size;
+  guint32 n_directories;
+  gint flags;
+} CacheInfo;
+
+gboolean _gtk_icon_cache_validate (CacheInfo *info);
+
+G_END_DECLS
+
+#endif  /* __GTK_ICON_CACHE_VALIDATOR_H__ */
index cdd6db3b1017c1bb978701040a470b62d6765d89..d11f11f909865dbb4413041119d1fdb2a1a05c85 100644 (file)
 #include <glib/gstdio.h>
 #include <gdk-pixbuf/gdk-pixdata.h>
 #include <glib/gi18n.h>
+#include "gtkiconcachevalidator.h"
 
 static gboolean force_update = FALSE;
 static gboolean ignore_theme_index = FALSE;
 static gboolean quiet = FALSE;
 static gboolean index_only = FALSE;
+static gboolean validate = FALSE;
 static gchar *var_name = "-";
 
+/* Quite ugly - if we just add the c file to the
+ * list of sources in Makefile.am, libtool complains.
+ */
+#include "gtkiconcachevalidator.c"
+
 #define CACHE_NAME "icon-theme.cache"
 
 #define HAS_SUFFIX_XPM (1 << 0)
@@ -1396,6 +1403,32 @@ write_file (FILE *cache, GHashTable *files, GList *directories)
   return TRUE;
 }
 
+static gboolean
+validate_file (const gchar *file)
+{
+  GMappedFile *map;
+  CacheInfo info;
+
+  map = g_mapped_file_new (file, FALSE, NULL);
+  if (!map)
+    return FALSE;
+
+  info.cache = g_mapped_file_get_contents (map);
+  info.cache_size = g_mapped_file_get_length (map);
+  info.n_directories = 0;
+  info.flags = CHECK_OFFSETS|CHECK_STRINGS|CHECK_PIXBUFS;
+
+  if (!_gtk_icon_cache_validate (&info)) 
+    {
+      g_mapped_file_free (map);
+      return FALSE;
+    }
+  
+  g_mapped_file_free (map);
+
+  return TRUE;
+}
+
 static void
 build_cache (const gchar *path)
 {
@@ -1448,6 +1481,13 @@ build_cache (const gchar *path)
       exit (1);
     }
 
+  if (!validate_file (tmp_cache_path))
+    {
+      g_printerr (_("The generated cache was invalid.\n"));
+      g_unlink (tmp_cache_path);
+      exit (1);
+    }
+
   cache_path = g_build_filename (path, CACHE_NAME, NULL);
 
 #ifdef G_OS_WIN32
@@ -1542,6 +1582,7 @@ static GOptionEntry args[] = {
   { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't include image data in the cache"), NULL },
   { "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" },
   { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL },
+  { "validate", 'v', 0, G_OPTION_ARG_NONE, &validate, N_("Validate existing icon cache"), NULL },
   { NULL }
 };
 
@@ -1569,6 +1610,28 @@ main (int argc, char **argv)
   path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
 #endif
   
+  if (validate)
+    {
+       gchar *file = g_build_filename (path, CACHE_NAME, NULL);
+
+       if (!g_file_test (file, G_FILE_TEST_IS_REGULAR))
+         {
+            if (!quiet)
+              g_printerr (_("File not found: %s\n"), file);
+            exit (1);
+         }
+       if (!validate_file (file))
+         {
+           if (!quiet)
+             g_printerr (_("Not a valid icon cache: %s\n"), file);
+           exit (1);
+         }
+       else 
+         {
+           exit (0);
+         }
+    }
+
   if (!ignore_theme_index && !has_theme_index (path))
     {
       g_printerr (_("No theme index file in '%s'.\n"